"""
Module to help implementing a Command Server.

Commands are sent as a tuple of C{(command, data)} in its pickled form.



Last Modified: $Id: commandserver.py 43 2005-06-14 07:42:58Z dafny $

@author: U{Gustavo Sverzut Barbieri <barbieri@gmail.com>}
@license: GNU GPL
@copyright: (C) 2005 Gustavo Sverzut Barbieri
@version: $Rev: 43 $
"""


import sys
import struct
import commands
import cPickle as pickle
import socket
from SocketServer import StreamRequestHandler
from debug import debug

def command_send( sendfunc, command, data ):
    """Send C{(command, data)} using C{sendfunc()}.

    @param sendfunc: function to call to send serialized data. It should accept
                     at least one parameter, a string.
    @param command: command to send. Needs to support L{cPickle.dumps}.
    @param data: data do send as command argument. Needs to support
                 L{cPickle.dumps}.

    @see: L{cPickle.dumps}
    """
    string = pickle.dumps( ( command, data ) )
    fmt = '!l%ds' % len( string )
    d = struct.pack( fmt, len( string ), string )
    sendfunc( d )
# command_send()


def command_recv( readfunc ):
    """Read C{(command, data)} using C{readfunc()}, returns it.

    @param readfunc: function to call to receive data, it should accept an
                     integer parameter to designate how many bytes to read and
                     return a string with read data.
    @return: command and its associate data in the form C{(command, data)}
    @rtype: L{tuple}
    """
    s = struct.calcsize( '!l' )
    l = readfunc( s )
    if not l:
	return None, None
    l = struct.unpack( '!l', l )[ 0 ]
    string = readfunc( l )
    return pickle.loads( string )
# command_recv()



class CommandRequestHandler( StreamRequestHandler ):
    """Serve commands

    Commands should be mapped to functions using the commands dict.

    The command protocol is really easy::

      <data size><pickled data>

    where:
      - B{<data size>}: is the size of <pickled data>
      - B{<pickled data>}: is the pickle.dumps() of a tuple: (command, data)
	 command is the command you want to give to server,
	 data is to be given to correspondent command function

    Classes that inherit from this one should declare the C{commands}
    attribute, that maps protocol commands to functions. It should declare this
    attribute in the C{setup()} method. For example::

       class MyCommandServer( CommandRequestHandler ):
           def setup( self ):
               CommandRequestHandler.setup( self )
               self.commands = { 'quit': self.do_quit }
           # setup()

           def do_quit( self, data ):
               print "user required quit with data: %s" % data
           # do_quit()
       # MyCommandServer

    To stop handling a request, raise L{StopIteration} or set server's C{run}
    attribute.

    Server should have C{run} attribute, this request just runs while it's
    True.
    """

    def __init__( self, *a, **ka ):
        StreamRequestHandler.__init__( self, *a, **ka )
    # __init__()

    def setup( self ):
        StreamRequestHandler.setup( self )
    # setup()


    def recv( self ):
        """Receive command and its data from client.

        @return: a tuple C{(command, data)}.

        @see: L{commandserver.command_recv}
        """
        return command_recv( self.request.recv )
    # recv()


    def send( self, command, data ):
        """Send command and its data to client.

        @param command: any value from L{commands}.
        @param data: any python object to send as data.

        @see: L{command_send}
        """
        command_send( self.request.send, command, data )
    # send()


    def send_ack( self ):
        self.send( commands.ACK, None )
    # send_ack()


    def recv_ack( self ):
        self._debug( 2, "waiting for ACK..." )
        cmd, data = self.recv()
        if cmd == commands.ACK:
            self._debug( 2, "ACK received!" )
            return True
        else:
            self._debug( 0, "could not receive ACK, got %s instead." % cmd )
            return False
    # recv_ack()


    def handle( self ):
        """Handle a connection to the client.

        Classes that inherit from this should have an attribute C{commands}
        that is a L{dict} that maps protocol commands to functions. These
        functions should accept one parameter, the command data.

        To finish the connection to the server, functions may raise
        L{StopIteration}.

        After every issued command an ACK command is sent.
        """
	self._debug( 1, "Handle connection." )
	try:
	    while self.server.run:
		self._debug( 3, "loop" )
		command, data = self.recv()
		if not command:
		    self._debug( 2, "No command... Socket is closed?" )
		    raise StopIteration
		try:
		    func = self.commands[ command ]
		    self._debug( 2, "   Command: %s (%s), Data: %s" % \
				 ( command, func.func_name, data ) )
		    func( data )

                    self.send_ack()
                    if not self.recv_ack():
                        self._debug( 0, "Client should send ACK!" )
                        return

		except KeyError, e:
		    self._debug( 0,
                                 'Command "%s" doesn\'t exists.' % command )
	except StopIteration:
	    pass
	self._debug( 1, "End request." )
    # handle()




    def _debug( self, level, message ):
        """Print message to standard error based on current debug level and
        message level.

        Message is only printed if C{level >= debug.level}.

        @param level: message level
        @type  level: L{int}
        @param message: what to print.

        @see: L{debug.debug}
        """
        debug( level,"%s: %s:%s: %s" % \
               ( self.__class__.__name__,
                 self.client_address[ 0 ],
                 self.client_address[ 1 ],
                 message ) )
    # _debug()
# CommandRequestHandler
